package models;

import play.mvc.*;
import play.libs.*;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import akka.actor.*;
import static akka.pattern.Patterns.ask;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.util.*;
import models.msgs.*;
import static java.util.concurrent.TimeUnit.*;

public class Table extends UntypedActor {

	private static int startPlayer;
	private static ArrayList<String> playerList = new ArrayList();
	private static ArrayList<Hand> handList = new ArrayList();
	private static ArrayList<Boolean> foldList = new ArrayList();
	private static ArrayList<Boolean> changeList = new ArrayList();
	private static boolean error = false;
	private static boolean firstAuction = false;
	private static boolean secondAuction = false;
	private static boolean allIn;
	private static boolean changeTime = false;

	private static boolean betMade;
	
	private static int roundNumber = 0;
	private static boolean gameOn;
	
	private static int currPlayer;
    private static int pool;
    private static int checkin;
    private static int currBet;
    private static int[] auction;
    
    public static boolean getGameOn (){
    	return gameOn;
    }
    // Default table.
    static ActorRef defaultTable = Akka.system().actorOf(Props.create(Table.class));
    
    // Members of this table.
    static Map<String, ActorRef> members = new HashMap<String, ActorRef>();
       
    //deck
    static Deck deck = new Deck();

	
  
    
    
    public static void join(final String name, WebSocket.In<JsonNode> in, WebSocket.Out<JsonNode> out ) throws Exception{

        // Send the Join message to the table
        String result = (String)Await.result(ask(defaultTable,new Join(name), 1000), Duration.create(1, SECONDS));
                
        if("OK".equals(result)) {
        	
            ActorRef player =  Akka.system().actorOf(
                    Props.create(Human.class, name, in, out,  defaultTable ));
            
            members.put(name, player);  
            playerList.add(name);
            handList.add(new Hand());
            foldList.add(false);
            changeList.add(false);
            members.keySet();
            int mAmount = members.size(); 
            String text =  "Actor has been added to Table, Current Amount of Player:" + mAmount; 
             
            notifyAll(new Info(text,"Player Quantity"));  
            
            if(mAmount == Config.getPlayerAmount()){
            	roundNumber = 1;
            	text = " Enough number of players, Let's Start";
            	notifyAll(new Info(text, "Game Init"));
            	gameOn = true;
            }
            if (gameOn){
            	gameInit();
            	}	
        }
	}
        
    
	private static void gameInit() {
		// TODO Auto-generated method stub
		roundNumber = 0;
		Random rand = new Random();
		startPlayer = rand.nextInt(Config.getPlayerAmount());
		currPlayer = startPlayer;
		auction = new int[Config.getPlayerAmount()];
		for(ActorRef member : members.values()){
			member.tell(new InitGame(Config.getEntryFee(), Config.getStartCash()), defaultTable);
			
		}
		//for(; roundNumber < Config.roundAmount; roundNumber++){
			nextRound();
		//}
	}


	private static void nextRound() {
		for(ActorRef member : members.values()){
			member.tell(new PrepareRound(), defaultTable);
		}
		/*if(checkin == Config.getPlayerAmount()){
		notifyAll(new Info("Entry fee has been taken from players, let's start first round of auction.", "Table"));
		notifyAll(new Info (" Player " + playerList.get(currPlayer % Config.getPlayerAmount()) + "starts","Table"));
		}*/
		
			
		
		
	}//TODO rundy

    public static void quit(final String username) throws Exception{

        //to do
    }
    
       
    public void onReceive(Object message) throws Exception {
        
        if(message instanceof Join) {
            
            Join join = (Join) message;
                  getSender().tell("OK", getSelf());
            
        } else if (message instanceof Message)
        {   
        	boolean beginAuction = false;
        	int totalCash = ((Message) message).getTotalCash();
        	String txt = ((Message) message).getMessage();  
        	String author = ((Message) message).getName();
        	
        	if (!(txt.equals("help")||txt.contains("!change")||txt.equals("!raise") || txt.equals("!bet") || txt.equals("!check") ||
        			txt.equals("!fold") || txt.equals("!call")||txt.equals("!hand"))) { 
        		notifyAll(new Info(txt,author));
        	}
        	else if(txt.equals("!hand")){
        		int handSender = playerList.indexOf(author);
        		for(int cardIndex = 0; cardIndex < 5; cardIndex++){
        			String cardTxt = handList.get(handSender).cards[cardIndex].toString();
        			notifyPlayer(new Info(cardTxt, Integer.toString(cardIndex+1)),author);
        		}
        	}
        	else if(txt.equals("help")){
        		txt = "masz pomoc synu";
        		
        		notifyPlayer(new Info(txt,((Message) message).getName()), ((Message) message).getName());
        	}
        	else if(txt.contains("change")&&changeTime){
        			
                   int playerNum = playerList.indexOf(author);
        			
                    String strToCut = txt;


                    if (txt.contains("!change")) {
                        if (txt.contains("!change0")) {
                            notifyAll(new Info("Player " + author + " doesn't change cards", "Table"));
                            currPlayer++;
                      
                        } else if (txt.contains("!change1")) {
                            notifyAll(new Info("Player " + author + " wants to change 1 card", "Table"));
                            try {
                                int cardIndex = Integer.parseInt(strToCut.substring(9,10));
                                deck.returnToDeck(handList.get(playerNum).cards[cardIndex-1]);
                                deck.Shuffle();
                                Card card = deck.drawFromDeck();
                                handList.get(playerNum).cards[cardIndex-1] = card;
                                notifyPlayer(new SendCard(cardIndex-1, card), author);
                            } catch (StringIndexOutOfBoundsException e) {
                                notifyAll(new Info("Error in getting cards to change. Try again.", "Table"));
                                currPlayer--;
                            }

                            currPlayer++;
                        } else if (txt.contains("!change2")) {
                            notifyAll(new Info("Player " + author + " wants to change 2 cards", "Table"));

                            for (int cardID = 0 ; cardID < 2; cardID++) {
                                try {
                                    int cardIndex = Integer.parseInt(strToCut.substring(9+(cardID*2), 10+(cardID*2)));
                                    Card card = deck.drawFromDeck();
                                    handList.get(playerNum).cards[cardIndex-1] = card;
                                    deck.returnToDeck(handList.get(playerNum).cards[cardIndex]);
                                    notifyPlayer(new SendCard(cardIndex-1, card), author);
                                } catch (StringIndexOutOfBoundsException e) {
                                    notifyAll(new Info("Błędne dane karty. Spróbuj jeszcze raz.", "Table"));
                                    currPlayer--;
                                    break;
                                }
                            }

                            currPlayer++;
                        } else if (txt.contains("!change3")) {
                            notifyAll(new Info("Player " + author + " wants to change 3 cards", "Table"));
                            
                            for (int cardID = 0 ; cardID < 3; cardID++) {
                                try {
                                    int cardIndex = Integer.parseInt(strToCut.substring(9+(cardID*2), 10+(cardID*2)));
                                    Card card = deck.drawFromDeck();
                                    deck.returnToDeck(handList.get(playerNum).cards[cardIndex-1]);
                                    deck.Shuffle();
                                    notifyPlayer(new SendCard(cardIndex-1, card), author);
                                    handList.get(playerNum).cards[cardIndex-1] = card;
                                } catch (StringIndexOutOfBoundsException e) {
                                    notifyAll(new Info("Błędne dane karty. Spróbuj jeszcze raz.", "Table"));
                                    currPlayer--;
                                }
                            }
                            
                            currPlayer++;
                        } else if (txt.contains("!change4")) {
                            notifyAll(new Info("Player " + author + " types !change4.", "Table"));

                            for (int cardID = 0 ; cardID < 4; cardID++) {
                                try {
                                    int cardIndex = Integer.parseInt(strToCut.substring(9+(cardID*2), 10+(cardID*2)));
                                    Card card = deck.drawFromDeck();
                                    deck.returnToDeck(handList.get(playerNum).cards[cardIndex-1]);
                                    deck.Shuffle();
                                    handList.get(playerNum).cards[cardIndex-1] = card;
                                    notifyPlayer(new SendCard(cardIndex-1, card), author);
                                } catch (StringIndexOutOfBoundsException e) {
                                    notifyAll(new Info("Błędne dane karty. Spróbuj jeszcze raz.", "Table"));
                                    currPlayer--;
                                }
                            }
                            
                            currPlayer++;
                        } else {
                            notifyPlayer(new Info("Wrong command. Try !change[0,1,2,3,4] <card numbers splitted with commas>", "Table"), author);
                           
                        }
                  
                        
                        if (currPlayer % Config.getPlayerAmount() == startPlayer) {
                            changeTime = false;
                            secondAuction = true;

                            notifyAll(new Info("End of changing cards. Second round of auction has began.  ", "Table"));
                        }

                    } else {
                        notifyPlayer(new Info("Wrong command. Try !change[0,1,2,3,4] <card numbers splitted with commas>", "Table"), author);
                    }

        		
        		
        	}
        	
        			else if(playerList.indexOf(author) == currPlayer % Config.getPlayerAmount()&&(firstAuction || secondAuction)) {
        		
        			beginAuction = true;
        			
        			if(txt.equals("!raise") || txt.equals("!bet") || txt.equals("!check") ||
                			txt.equals("!fold") || txt.equals("!call")) {
        				
        			if(auction[currPlayer % Config.getPlayerAmount()] != -3) {
        				
        				
        				if (txt.equals("!raise")){
        					if(!allIn){
        						
        					
        					if(betMade){
        					
        					notifyAll(new Info ("Player " + author + " raises ", "Table"));
        					currBet += Config.getPlayerBet();
        					int bet = currBet - auction[currPlayer % Config.getPlayerAmount()];
        					pool += bet;
        					notifyPlayer(new GetCash(author, bet), author); 
        					totalCash -= bet;
        					notifyPlayer(new Info(" your cash  " + totalCash + "$", "Table"),author);
        					auction[currPlayer%Config.getPlayerAmount()] = currBet;
        					currPlayer++;
        					
        					}
        					else {
        						notifyAll(new Info("Unable to raise, if bet didn't occur ", "Table"));
        					}
        				}
        					else{
        						notifyPlayer(new Info("Allin has been played. You can only play fold or allin also","Table"),author);
        						
        					}
        				}
        				
        			
        				else if (txt.equals("!check")){
        					if(!allIn){
        						
        					
        						if(!betMade){
        							notifyAll(new Info ("Player " + author + " checks", "Table" ));
        							auction[currPlayer % Config.getPlayerAmount()] = Config.getEntryFee();
        							currBet = Config.getEntryFee();
        							
        							currPlayer++;
        						}
        					else {
        						error = true;
        						notifyPlayer(new Info("You are not able to check in this moment","Table"), author);
        					}
        					}
        					else{
        						notifyPlayer( new Info(" AllIn has been played. You can only fold or play allin also ","Table"),author);
        					}
        				}
        				else if (txt.equals("!bet")){
        					if(!allIn){
        						if(!betMade){
        						
        						
        						betMade = true;
        						notifyAll(new Info ("Player " + author + " bets","Table"));
        						
        						currBet += Config.getPlayerBet();
        						notifyAll(new Info("Current bet is " + currBet, "Table"));
        						int diffBet = currBet - auction[currPlayer % Config.getPlayerAmount()];
        						pool += diffBet;
        						notifyPlayer(new GetCash(author, diffBet), author); 
        						totalCash -= diffBet;
        						notifyPlayer(new Info(" your cash  " + totalCash + "$", "Table"),author);
        						auction[currPlayer%Config.getPlayerAmount()] = currBet;
        							//TODO blokady zeby sie nie zaciagac
        						currPlayer++;
        					}
        					else{
        						error = true;
        						notifyPlayer(new Info("unable to do, bet has been made earlier, try !raise, !call, !allIn","Table"), author);
        					}
        					}
        					else{
        						notifyPlayer(new Info("Allin has been played. You can only play fold or allin also","Table"),author);
        					}
        				}
        				else if(txt.equals("!allin")){
        					allIn = true;
        					currBet += totalCash - auction[currPlayer % Config.getPlayerAmount()];
        					pool += currBet;
        					
        				}
        				else if (txt.equals("!call")) {
        				if(!allIn){
        					if(betMade){
        						
        						
        						notifyAll(new Info ("Player " + author + " calls,", "Table"));
        						int bet = currBet - auction[currPlayer % Config.getPlayerAmount()];
        						notifyPlayer(new GetCash(author, bet),author);
        						totalCash -= bet;
        						notifyPlayer(new Info(" your cash - " + totalCash + "$", "Table"),author);
        						auction[currPlayer%Config.getPlayerAmount()] = currBet;

        						currPlayer++;
        						
        					}
        					else {
        						error = true;
        						notifyPlayer(new Info("Unable to call before bet!", "Table"), author);
        					}
        				}
        				else{
        					notifyPlayer(new Info("Allin has been played. You can only play fold or allin also","Table"),author);
        				}
        					
        				}
        				else if( txt.equals("!fold")){
        					
        					
        					notifyAll(new Info("Player " + author + " folds", "Table"));
        					auction[currPlayer % Config.getPlayerAmount()] = -3;
        					currPlayer++;
        				}

        			}
        			else{
        				notifyAll(new Info(" you've already folded, wait for next round","Table"));
        			}
        			
        						

        		}
		
        	}
        		else{
        			txt = "it's not your time to do auction moves";
        			notifyPlayer(new Info(txt,((Message) message).getName()), ((Message) message).getName());
        		}
    			if(auctionEnd() && !error && beginAuction && firstAuction){
					notifyAll(new Info("End of first round of auction", "Table"));
					changeTime = true;
					error = false;
					betMade = false;
				}	
    			if(auctionEnd() && !error && beginAuction && secondAuction){
    				
    			}
    				//TODO naprawic zeby niepowolona komenda nie wywolywala konca gry
    			
        }	
        
	
        else if (message instanceof SendCard){
        	
        }
        else if(message instanceof Entry){
        	
        	String name = ((Entry) message).getName();
        	int playerNum = playerList.indexOf(name);
        	notifyPlayer(new Info (" Player " + playerList.get(currPlayer % Config.getPlayerAmount()) + "starts","Table"), name);
        	//ActorRef member = members.get(name);
        	
        	if(((Entry) message).getBankrupt())
        		notifyPlayer(new Info("you are a bankrupt" , "Table"), name);
        		//gameOver(); TODO
        	else{

        		deck.Shuffle();
        		notifyPlayer(new Info("your cash is " + ((Entry) message).getTotalCash() + "$" , "Table"), name);
        		//auction[playerNum] = Config.getEntryFee();
        		//currBet = Config.getEntryFee();
        		Hand hand = new Hand(deck);
        		handList.add(playerNum, hand);
        		notifyPlayer(new SendHand(hand),name);
    			for(int i = 0; i < 5; i++){	
    		
    				notifyPlayer(new Info(hand.cards[i].toString(),Integer.toString(i+1)), name);
    				
    			}
    			pool += Config.getEntryFee();
    			checkin++;
    			if (checkin == Config.getPlayerAmount()){
    				String txt = "Entry fee taken, time to start auction";
    				notifyAll(new Info(txt, "Table"));
    				firstAuction = true;
    			}
        	}
        }
        
        
        
        else if (message instanceof Info)
        {   
        	String text = ((Info) message).getText();
        	String name = ((Info) message).getName();  
        	
        	notifyAll(new Info(text, name));
        	
        }  
        else if(message instanceof EndSend){
        	
        	String text = "You have right amount of cards do you want to change?";
        	notifyPlayer(new Info(text, "Game"),((EndSend) message).getName());	
        }
        
        else if(message instanceof Quit)  {            
           
            String name = ((Quit)message).getName();   
            int playerNum = playerList.indexOf(name);
            foldList.remove(playerNum);
            playerList.remove(playerNum);
            handList.remove(playerNum);
            members.remove(name);
            
            playerList.remove((name));
            
            int mAmount = members.size();
            String text = "Actor has left the game, current amount of players: " + mAmount;
            notifyAll(new Info(text, "Player Quantity"));
            if(mAmount != Config.getPlayerAmount()){
            	if(gameOn){
            		gameOn = false;
            		text = "too few players, game interrupted";
            	}
            	else{
            	text = "too few players, awaiting for players";
            	}
            	notifyAll(new Info(text," player quantity"));
            }
            
          
        
        } else {
            unhandled(message);
        }
    			
        
    }


	private boolean auctionEnd() {
		
		for(int i = 0; i < auction.length; i++)
			if(!(auction[i]== -3) && !(auction[i] == currBet))
				return false;
		
		return true;
	}


	private static void extracted() {
		notifyAll(new Info("Unable to join to the table", "Warn"));
	}
    static public void notifyPlayer(Object msg, String name){
    	ActorRef member = members.get(name);
    	member.tell(msg, defaultTable) ;
    }
        
    static public void notifyAll(Object msg)
    {
        for (ActorRef member : members.values())
        {
            member.tell(msg, defaultTable);
            
        }
    }
    
    
    
    
}

